package edu.thu.studio.ormdesigner.editor;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.net.URI;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.gef.DefaultEditDomain;
import org.eclipse.gef.GraphicalViewer;
import org.eclipse.gef.commands.Command;
import org.eclipse.gef.editparts.ZoomManager;
import org.eclipse.gef.palette.PaletteRoot;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.swt.custom.CTabFolder;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.IPageLayout;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.ide.FileStoreEditorInput;
import org.eclipse.ui.ide.IGotoMarker;
import org.eclipse.ui.part.MultiPageEditorPart;

import edu.thu.studio.ormdesigner.editor.contributor.ORMModelActionBarContributor;
import edu.thu.studio.ormdesigner.editor.palette.ORMPaletteRoot;
import edu.thu.studio.ormdesigner.model.ORMModel;
import edu.thu.studio.ormdesigner.model.diagram.Diagram;
import edu.thu.studio.ormdesigner.outline.OutlinePage;
import edu.thu.studio.ormdesigner.outline.command.UpdateDiagramCommand;
import edu.thu.studio.ormdesigner.outline.dialog.DiagramDialog;
import edu.thu.studio.ormdesigner.persistent.Persistent;
import edu.thu.studio.ormdesigner.util.Utils;

public class ORMMultiPageEditor extends MultiPageEditorPart {
    
    private IFile inputFile;
    
    private String inputFilePath;
    
    private ORMModel ormModel;
    
    private EditorEditPartFactory editPartFactory;
    
    private OutlinePage outlinePage;
    
    private ElementStateListener fElementStateListener;
    
    private IGotoMarker gotoMaker;
    
    private Map<IMarker, Object> markedObjectMap = new HashMap<IMarker, Object>();
    
    private DefaultEditDomain editDomain;
    
    private PaletteRoot paletteRoot;
    
    public ORMMultiPageEditor() {
        this.gotoMaker = new GotoMarker(this);
        this.editDomain = new DefaultEditDomain(this);
        this.paletteRoot = new ORMPaletteRoot();
    }
    
    /**
     * {@inheritDoc}
     */
    @Override
    protected void createPages() {
        InputStream in = null;
        
        try {
            IEditorInput input = this.getEditorInput();
            
            if (input instanceof IFileEditorInput) {
                this.inputFile = ((IFileEditorInput) input).getFile();
                this.inputFilePath = this.inputFile.getLocation().toOSString();
                
                this.setPartName(this.inputFile.getName());
                
                if (!this.inputFile.isSynchronized(IResource.DEPTH_ONE)) {
                    this.inputFile.refreshLocal(IResource.DEPTH_ONE, new NullProgressMonitor());
                }
                
                in = this.inputFile.getContents();
                
            } else {
                URI uri = ((FileStoreEditorInput) input).getURI();
                File file = new File(uri);
                this.inputFilePath = file.getCanonicalPath();
                
                this.setPartName(file.getName());
                
                in = new FileInputStream(file);
            }
            
            Persistent persistent = Persistent.getInstance();
            this.ormModel = persistent.load(in);
            
        } catch (Exception e) {
            Utils.showExceptionDialog(e);
            
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (Exception e) {
                    Utils.showExceptionDialog(e);
                }
            }
        }
        
        if (this.ormModel == null) {
            this.ormModel = new ORMModel();
        }
        
        this.ormModel.checkAndInit();
        
        this.ormModel.setEditor(this);
        
        this.editPartFactory = new EditorEditPartFactory();
        this.outlinePage = new OutlinePage(this.ormModel, this.editDomain);
        
        this.initGraphicalEditorPages();
        
        this.addMouseListenerToTabFolder();
        
    }
    
    @Override
    protected Composite createPageContainer(Composite parent) {
        try {
            IWorkbenchPage page = this.getSite().getWorkbenchWindow().getActivePage();
            
            if (page != null) {
                page.showView(IPageLayout.ID_OUTLINE);
            }
            
        } catch (PartInitException e) {
            Utils.showExceptionDialog(e);
        }
        
        return super.createPageContainer(parent);
    }
    
    /**
     * {@inheritDoc}
     */
    @Override
    public void doSave(IProgressMonitor monitor) {
        ZoomManager zoomManager = (ZoomManager) this.getActiveEditor().getAdapter(ZoomManager.class);
        double zoom = zoomManager.getZoom();
        this.ormModel.setZoom(zoom);
        
        Persistent persistent = Persistent.getInstance();
        
        try {
            
            InputStream source = persistent.createInputStream(this.ormModel);
            
            if (this.inputFile != null) {
                if (!this.inputFile.exists()) {
                    this.inputFile.create(source, true, monitor);
                    
                } else {
                    this.inputFile.setContents(source, true, false, monitor);
                }
            }
            
        } catch (Exception e) {
            Utils.showExceptionDialog(e);
        }
        
        for (int i = 0; i < this.getPageCount(); i++) {
            IEditorPart editor = this.getEditor(i);
            editor.doSave(monitor);
        }
        
    }
    
    @Override
    public void doSaveAs() {
    }
    
    @Override
    public boolean isSaveAsAllowed() {
        return false;
    }
    
    @Override
    public void pageChange(int newPageIndex) {
        ORMGraphicalEditor currentEditor = (ORMGraphicalEditor) this.getActiveEditor();
        currentEditor.removeSelection();
        currentEditor.resetEditDomain();
        super.pageChange(newPageIndex);
        this.ormModel.setCurrentIndex(newPageIndex);
    }
    
    @Override
    public ORMGraphicalEditor getActiveEditor() {
        return (ORMGraphicalEditor) super.getActiveEditor();
    }
    
    public void selectRootEditPart() {
        GraphicalViewer viewer = this.getActiveEditor().getGraphicalViewer();
        viewer.deselectAll();
        viewer.appendSelection(viewer.getRootEditPart());
    }
    
    @Override
    public void init(IEditorSite site, IEditorInput input) throws PartInitException {
        super.init(site, input);
        this.fElementStateListener = new ElementStateListener(this);
    }
    
    @Override
    public void dispose() {
        this.fElementStateListener.disposeDocumentProvider();
        super.dispose();
    }
    
    @Override
    protected void setInputWithNotify(IEditorInput input) {
        super.setInputWithNotify(input);
    }
    
    /**
     * 添加 package 的编辑tab页
     * 
     * @param diagram
     * @throws PartInitException
     */
    public void addGraphicalEditor(Diagram diagram) throws PartInitException {
        int index = this.ormModel.getDiagrams().getObjectList().indexOf(diagram);
        ORMGraphicalEditor editor = new ORMGraphicalEditor(diagram, this.editPartFactory, this.outlinePage,
            this.editDomain, this.paletteRoot);
        
        this.addPage(editor, this.getEditorInput());
        
        this.setPageText(index, diagram.getName());
        
        if (this.getActiveEditor() != null) {
            this.getActiveEditor().resetEditDomain();
        }
    }
    
    /**
     * 移除 package 的编辑tab页
     * 
     * @param diagram
     * @throws PartInitException
     */
    public void removeGraphicalEditor(Diagram diagram) {
        int index = this.ormModel.getDiagrams().getObjectList().indexOf(diagram);
        IEditorPart editorPart = this.getEditor(index);
        editorPart.dispose();
        this.removePage(index);
        this.editDomain.loadDefaultTool();
    }
    
    public void updateGraphicalEditor(Diagram diagram) {
        int index = this.ormModel.getDiagrams().getObjectList().indexOf(diagram);
        this.setPageText(index, diagram.getName());
    }
    
    private void initGraphicalEditorPages() {
        List<Diagram> diagrams = this.ormModel.getDiagrams().getObjectList();
        for (int i = 0; i < diagrams.size(); i++) {
            try {
                this.addGraphicalEditor(diagrams.get(i));
            } catch (PartInitException e) {
                e.printStackTrace();
            }
        }
        this.setActivePage(0);
        
        ORMGraphicalEditor activeEditor = (ORMGraphicalEditor) this.getActiveEditor();
        ZoomManager zoomManager = (ZoomManager) activeEditor.getAdapter(ZoomManager.class);
        zoomManager.setZoom(this.ormModel.getZoom());
        
        setRetargetActions(activeEditor);
    }
    
    private void addMouseListenerToTabFolder() {
        CTabFolder tabFolder = (CTabFolder) this.getContainer();
        
        tabFolder.addMouseListener(new MouseAdapter() {
            
            @Override
            public void mouseDoubleClick(MouseEvent mouseevent) {
                
                Diagram diagram = ormModel.getCurrrentDiagram(getActivePage());
                
                if (diagram != null) {
                    DiagramDialog dialog = new DiagramDialog(PlatformUI.getWorkbench().getActiveWorkbenchWindow()
                        .getShell(), diagram);
                    
                    if (dialog.open() == IDialogConstants.OK_ID) {
                        UpdateDiagramCommand command = new UpdateDiagramCommand(ormModel, diagram, dialog.getResult());
                        execute(command);
                    }
                }
                
                super.mouseDoubleClick(mouseevent);
            }
        });
    }
    
    private void execute(Command command) {
        ORMGraphicalEditor selectedEditor = (ORMGraphicalEditor) this.getActiveEditor();
        selectedEditor.getGraphicalViewer().getEditDomain().getCommandStack().execute(command);
    }
    
    @Override
    @SuppressWarnings("rawtypes")
    public Object getAdapter(Class type) {
        if (type == ORMModel.class) {
            return this.ormModel;
            
        } else if (type == IGotoMarker.class) {
            return this.gotoMaker;
        }
        
        return super.getAdapter(type);
    }
    
    public Object getMarkedObject(IMarker marker) {
        return markedObjectMap.get(marker);
    }
    
    public void setMarkedObject(IMarker marker, Object markedObject) {
        this.markedObjectMap.put(marker, markedObject);
    }
    
    public void clearMarkedObject() {
        this.markedObjectMap.clear();
    }
    
    public void refreshProject() {
        if (this.inputFile != null) {
            IProject project = this.inputFile.getProject();
            try {
                project.refreshLocal(IResource.DEPTH_INFINITE, null);
            } catch (CoreException e) {
                Utils.showExceptionDialog(e);
            }
        }
    }
    
    public String getDiagramFilePath() {
        return this.inputFilePath;
    }
    
    public String getBasePath() {
        if (this.inputFile != null) {
            return this.inputFile.getProject().getLocation().toOSString();
        }
        
        return new File(this.inputFilePath).getParent();
    }
    
    public String getDefaultCharset() {
        if (this.inputFile != null) {
            IProject project = this.inputFile.getProject();
            
            try {
                Charset defautlCharset = Charset.forName(project.getDefaultCharset());
                return defautlCharset.displayName();
                
            } catch (CoreException e) {
            }
        }
        
        return Charset.defaultCharset().displayName();
    }
    
    public ORMModel getORMModel() {
        return ormModel;
    }
    
    public void setRetargetActions(ORMGraphicalEditor newEditor) {
        ORMModelActionBarContributor actionBarContributor = newEditor.getActionBarContributor();
        actionBarContributor.initRetargetActions(this);
    }
    
}
